import socket,select
import time,struct,os
from helpFunc import *
from base import *
import logging
from logging.handlers import RotatingFileHandler
logger = None
from datetime import datetime

def logWarn(st):
    global logger
    if not logger:
        logger = logging.getLogger('ser')
        now = datetime.now()        
        ti = now.strftime("%Y-%m-%d-%H:%M:%S")        
        handler = RotatingFileHandler('serLog/%s||%s.log'%(os.getpid(),ti), maxBytes=10*1024, backupCount=1)
        formatter = logging.Formatter("%(asctime)s %(message)s",
                                      "%Y-%m-%d %H:%M:%S")
        handler.setFormatter(formatter)
        logger.addHandler(handler)
    logger.warning(st)

class ser(base):
    def __init__(self,dataPort,numPort,onePakcLen,totalSendLen,
                 packEffectTime,timeoutInter,lp,k):
        super().__init__(dataPort,numPort,onePakcLen,packEffectTime,timeoutInter,k)
        self.staTime = time.monotonic()
        self.m = {}
        self.sendLen = totalSendLen
        self.recvMap = {}
        self.recvPos = 0   
        self.iniServerSocks(lp)
        self.peerMaxGot = 0       
        self.blackIp = {}
        
    def getOneStatus(self):
        k = self.recvMap.keys()
        if not k:
            ma = self.recvPos
        else:        
            ma = max(k)
        co = 0
        b = 0
        ss = b''
        for i in range(self.recvPos,ma+1):
            v = 0
            if i in self.recvMap:
                v = 1
            co+=1
            if co==8:
                b |= v
                ss += bytes([b])
                co=0
                b = 0
            else:
                b |= v 
                b <<= 1
        if co!=0:
            b <<= (7-co)
            ss += bytes([b])
        return struct.pack('Q',self.recvPos)+struct.pack('Q',ma)+ss         
    
    def iniServerSocks(self,lp):
        for i in lp:    
            so=socket.socket(type=socket.SOCK_DGRAM)
            add = (serverBindIp,i)
            so.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            so.bind(add)
            if isWindows:
                kk = so
            else:
                kk = so.fileno()            
            self.m[kk]={'pysock':so,'id':b'','add':None,'sendList':[],'hasSta':0} 
            
    def getNSendAhead(self,n):
        if n<=0:
            return []
        ss = self.peerMaxGot+1
        l = []
        while ss < self.sendPos and ss in self.sendMap:
            if self.sendMap[ss]['peerGot']:
                ss+=1
                continue
            if self.sendMap[ss]['sendTime']>self.maxAskTime:
                ss+=1
                continue
            l.append(ss)
            self.sendMap[ss]['sendTime'] += 1
            ss +=1
            if len(l)==n:
                break
        return l
    
    def dealRecvPack(self,fd):
        so = self.m[fd]['pysock']
        try:
            a,b = so.recvfrom(udpReadLen)
        except:
            return -1
        r,self.m[fd]['id'],ll,hasSta = self.dealRecv(a)
        if not r:
            ip = b[0]
            if ip not in self.blackIp:
                self.blackIp[ip] = 0
            self.blackIp[ip] += 1          
            return -1
        self.lastRecvTime = time.monotonic()
        if len(a)>self.maxGotLen:
            self.maxGotLen = len(a)                
        self.m[fd]['sendList'] = ll
        self.m[fd]['hasSta'] = hasSta
        for jj in ll:
            if jj>self.peerMaxGot:
                self.peerMaxGot = jj
            if jj in self.sendMap:
                self.sendMap[jj]['peerGot']=True
        self.m[fd]['add'] = b    
        return hasSta
        
    def work(self):
        getList = []
        needSta_g = False
        if isWindows:
            rl,wl = self.rig(None)
            for k in self.m:
                rl.append(k)
            rl,wl,el = select.select(rl,wl,[],self.calWaitTime()/1000)
            self.dealFd(rl,wl)
            for one in rl:
                if one in self.m:                    
                    needSta = self.dealRecvPack(one)
                    if needSta<0:
                        continue
                    getList.append(one)
                    if needSta >0:
                        needSta_g = True
        else:
            po = select.poll()     
            for k in self.m:         
                po.register(k, select.POLLIN)
            self.rig(po)
            es = po.poll(self.calWaitTime())           
            for fd,e in es:   
                self.dealFd(fd, e)
                if fd in self.m and e&select.POLLIN: 
                    needSta = self.dealRecvPack(fd)  
                    if needSta<0:
                        continue
                    getList.append(fd)                    
                    if needSta>0:
                        needSta_g = True                    
        self.reFreshSendMap()      
        if needSta_g:
            sta = self.getOneStatus()
        else:
            sta = b''
        for one in getList:
            self.makePack(one,sta)
            
        if time.monotonic()-self.staTime>1:
            if self.blackIp:
                st = 'blackIps:%s'%(str(self.blackIp))
                logWarn(st)
                self.blackIp = {}
            if time.monotonic()-self.lastRecvTime>120:
                os._exit(0)                                
            self.staTime = time.monotonic()
            self.maxGotLen = self.maxSenLen = 0   
                        
        
    def makePack(self,fd,sta):
        w = structWrapper()
        si = self.m[fd]['hasSta']
        baseLen = 20+(len(self.m[fd]['id'])-2)        
        if si>0:
            sendNum = (self.sendLen-baseLen-len(sta))//(self.oneLen+2)
        else:
            sendNum = (self.sendLen-baseLen)//(self.oneLen+2)
            
        if sendNum>len(self.m[fd]['sendList']):
            sendNum = len(self.m[fd]['sendList'])            
        if sendNum<0:
            sendNum = 0    
        w.writeByte(len(self.m[fd]['id']))
        w.writeArr(self.m[fd]['id'])
        w.writeQ(self.sendPos)
        w.writeWord(self.maxGotLen)
        w.writeWord(self.maxSenLen)
        w.writeByte(sendNum)
        for i in range(sendNum):
            p = self.m[fd]['sendList'][i]
            if p not in self.sendMap or p>=self.sendPos:
                w.writeWord(0)
            else:
                w.writeWord(len(self.sendMap[p]['data']))
                w.writeArr(self.sendMap[p]['data'])
        if si>0:
            w.writeByte(1)
            w.writeWord(len(sta))
            w.writeArr(sta)
        else:
            w.writeByte(0)
        so = self.m[fd]['pysock']
        
        sendNum = (self.sendLen-len(w.data))//(10+self.oneLen)
        l = self.getNSendAhead(sendNum)
        w.writeByte(len(l))
        for i in l:
            w.writeQ(i)
            w.writeWord(len(self.sendMap[i]['data']))
            w.writeArr(self.sendMap[i]['data'])
        try:
            so.sendto(w.data,self.m[fd]['add'])
        except:
            pass
        if len(w.data)>self.maxSenLen:
            self.maxSenLen = len(w.data)

    def dealRecv(self,d):
        w = structWrapper(d)
        k = w.readArr(len(self.key))
        if k!= self.key:
            return False,b'',[],0
        idL = w.readByte()
        id = w.readArr(idL)
        peerPos = w.readQ()
        if peerPos > self.peerPos:
            l = 0
            for i in range(self.peerPos,peerPos):
                l += len(self.sendMap[i]['data'])
                del self.sendMap[i]
            self.peerPos = peerPos
            self.tellAddBuf(l)
        self.gotRate = w.readFloat()
        self.maxAskTime = self.calAskTime()
        hasData = w.readByte()
        if hasData == 1:
            packNum = w.readQ()
            packLen = w.readWord()
            pack = w.readArr(packLen)
            if packNum>=self.recvPos and packNum not in self.recvMap: 
                self.recvMap[packNum] = pack
                while self.recvPos in self.recvMap:
                    self.wrietBuf += self.recvMap[self.recvPos]
                    del self.recvMap[self.recvPos]
                    self.recvPos += 1
        hasSta = w.readByte()
        getNum = w.readByte()
        if getNum==0:
            return True,id,[],hasSta
        gList = []
        startNum = w.readQ()
        gList.append(startNum)
        for i in range(getNum-1):
            gList.append(startNum+w.readDWord())
        return True,id,gList,hasSta      
        
if __name__ == '__main__':
    import threading
    l = findNPorts(2,socket.SOCK_STREAM)
    def ww():
        a = ser(l[0],l[1],serverPackLen,serverSenLen,
                 con_packEffectTime,con_timeoutInternal,con_listenPorts,b'tr')
        while True:
            a.work()
    t = threading.Thread(target=ww)
    t.setDaemon(True)
    t.start()
    from testconn import testconn
    xx = testconn(l[0],l[1])
    
    
  
                
